﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Configuration;

using System.Globalization;
using System.Linq;
using System.Web.Caching;
using System.Xml.Linq;

using ProjectBase.Core;
using ProjectBase.Data.Xml;

using $prjsn$.Common.Providers;
using $prjsn$.Entity.Services;

namespace $safeprojectname$.Providers
{
    /// <summary>
    ///     Provides read access to the Menu.config file
    /// </summary>
    public class MenuConfigProvider : XmlDao, IMenuConfigProvider
    {
        #region members and const
        class Const
        {
            public const string AccessForListed = "accessForListed";
            public const string AccessIsGranted = "grant";

            public const string Action = "action";
            public const string ControllerOrNode = "controllerOrNode";
            public const string Dot = ".";
            public const string Expanded = "expanded";
            public const string Gif = "gif";

            public const string Href = "href";
            public const string ImagePath = "imagePath";
            public const string ImgFolder = "/i/menu/";

            public const string IsReadOnly = "isReadOnly";
            public const string IsVisible = "isVisible";
            public const string MenuItems = "MenuItems";
            public const string Name = "name";
            public const string RoleGranted = "roleGranted";
            public const string RoleGroup = "roleGroup";
            public const string Text = "text";
            public static readonly XNamespace Ns = "http://ProjectBase/Config.xsd";
            Const() { }
        }
        #endregion members and const

        protected override string DataSourcePath
        {
            get { return Global.CreateAbsolutePath(Global.Settings.ConfigurationFilePath.Menu); }
        }

        protected override bool DoSupportXInclude
        {
            get { return true; }
        }

        #region protected MenuConfig reader
        protected virtual IEnumerable<MenuItem> ReadMenuConfig()
        {
            var nodes = new List<MenuItem>();
            foreach (var element in Menu.Root.Elements(Const.Ns + Const.ControllerOrNode))
            {
                var item = CreateItem(element, nodes);
                if (item.IsNull())
                {
                    throw new ConfigurationErrorsException("Menu.config file(s) was not read correctly");
                }
            }
            return nodes;
        }

        protected virtual MenuItem CreateItem(XElement node, IList<MenuItem> nodes)
        {
            if (node.Attribute(Const.Name).IsNull()
                || node.Attribute(Const.Name).Value.IsEmpty())
            {
                throw new ConfigurationErrorsException("Menu.config file(s) element is missing 'name' attribute");
            }

            // I. SIMPLE conversion from XML elements into the properties
            var menuItem = new MenuItem
            {
                ID = node.Attribute(Const.Name).Value,
                // 1) controller or node name
            };

            // 2) isExpanded
            if (node.Attribute(Const.Expanded).Is())
            {
                bool isExpanded;
                if (node.Attribute(Const.Expanded).Value.TryParse(out isExpanded, CultureInfo.InvariantCulture))
                {
                    menuItem.IsExpanded = isExpanded;
                }
            }

            // 3) Href and Text
            menuItem.Href = node.Attribute(Const.Href).Is()
                                ? node.Attribute(Const.Href).Value
                                : menuItem.ID;
            menuItem.Text = node.Attribute(Const.Text).Is()
                                ? node.Attribute(Const.Text).Value
                                : menuItem.ID;

            // 4) ImagePath
            if (node.Attribute(Const.ImagePath).Is())
            {
                menuItem.ImagePath = node.Attribute(Const.ImagePath).Value;
                if (menuItem.ImagePath.IsEqual(Const.Dot))
                {
                    menuItem.ImagePath = Const.ImgFolder + menuItem.ID + Const.Dot + Const.Gif;
                }
            }

            // 5) Children
            foreach (var element in node.Elements(Const.Ns + Const.ControllerOrNode))
            {
                var item = CreateItem(element, nodes);
                item.ParentId = menuItem.ID; // 6) Parent
            }

            // 7) Roles
            menuItem.Roles = CreateRoles(node, menuItem.ID);

            nodes.Add(menuItem);
            return menuItem;
        }

        protected virtual IList<RoleAccess> CreateRoles(XElement node, string controllerName)
        {
            var roles = new List<RoleAccess>();

            CreateRolesFromGroups(node, controllerName, roles);

            // II. individual role definition
            foreach (var roleGranted in node.Elements(Const.Ns + Const.RoleGranted))
            {
                var role = CreateRole(roleGranted, controllerName);
                if (role.Is()) // was enough information to create the role
                {
                    roles.Add(role); // then append it
                }
            }
            return roles;
        }

        protected virtual void CreateRolesFromGroups(XElement node, string controllerName, List<RoleAccess> roles)
        {
            // global role groups defined? <roleGroup>
            var globalRoleGroups = Menu.Root
                .Elements(Const.Ns + Const.RoleGroup)
                .Where(e => e.Attribute(Const.Name).Is()) // with name="..." attr only
                .ToList();

            // I. role group definition
            foreach (var group in node.Elements(Const.Ns + Const.RoleGroup))
            {
                // has the group element name attribute?
                var nameAttr = @group.Attribute(Const.Name);

                if (nameAttr.IsNull())
                {
                    throw new ConfigurationErrorsException("Menu.config: <roleGroup> element is missing 'name' attribute ");
                }
                if (globalRoleGroups.IsEmpty())
                {
                    throw new ConfigurationErrorsException("Menu.config: <roleGroup> element can reference names defined as a Global <roleGroup> (at a root level of Menu.config)");
                }

                var isVisible = @group.Attribute(Const.IsVisible).IsNull();
                // missing attribute isVisible menas - IS visible
                if (!isVisible)
                {
                    if (!@group.Attribute(Const.IsVisible).Value.TryParse(out isVisible, CultureInfo.InvariantCulture))
                    {
                        isVisible = true; // the IsVisible was not convertable into the boolean - show this node
                    }
                }

                var roleGroup = globalRoleGroups
                    .FirstOrDefault(e => e.Attribute(Const.Name).Value.Equals(nameAttr.Value, StringComparison.Ordinal));

                if (roleGroup.IsNull())
                {
                    throw new ConfigurationErrorsException(
                        "Menu.config: <roleGroup> contains undefined roleGroup 'name' reference ");
                }

                // convert group to roles
                foreach (var roleGranted in roleGroup.Elements(Const.Ns + Const.RoleGranted))
                {
                    var role = CreateRole(roleGranted, controllerName);
                    if (role.Is()) // was enough information to create the role
                    {
                        role.IsVisible = isVisible;
                        roles.Add(role); // then append it
                    }
                }
            }
        }

        protected virtual RoleAccess CreateRole(XElement roleGranted, string controllerName)
        {
            if (roleGranted.Attribute(Const.Name).IsNull())
            {
                throw new ConfigurationErrorsException("Menu.config: <role> element is missing 'name' attribute ");
            }

            var role = new RoleAccess
            {
                RoleName = roleGranted.Attribute(Const.Name).Value,
                // 1) role name
                ControllerName = controllerName,
            };

            // 2) read only switch
            bool isReadOnly;
            if (roleGranted.Attribute(Const.IsReadOnly).Is()
                && roleGranted.Attribute(Const.IsReadOnly).Value.TryParse(out isReadOnly, CultureInfo.InvariantCulture))
            {
                role.IsReadOnly = isReadOnly;
            }

            // 3) more grained action settings needed?
            if (roleGranted.Attribute(Const.AccessForListed).Is())
            {
                // Create the list of enumerated actions
                var actions = new List<String>();
                foreach (var action in roleGranted.Elements(Const.Ns + Const.Action))
                {
                    if (action.Attribute(Const.Name).IsNull())
                    {
                        throw new ConfigurationErrorsException("Menu.config: <action> element is missing 'name' attribute ");
                    }
                    actions.Add(action.Attribute(Const.Name).Value);
                }

                // Should be listed actions granted or denied ?
                if (roleGranted.Attribute(Const.AccessForListed).Value.Equals(Const.AccessIsGranted,
                    StringComparison.OrdinalIgnoreCase))
                {
                    role.UseDenied = false; // listed actions are the only allowed
                    role.GrantedActions = actions;
                }
                else
                {
                    role.UseDenied = true; // listed actions are denied, any other is allowed
                    role.DeniedActions = actions;
                }
            }

            // 4) Is Visible
            role.IsVisible = roleGranted.Attribute(Const.IsVisible).IsNull();
            // no attribute IsVisible means that it is visible
            if (!role.IsVisible) // there is IsVisible attribute
            {
                bool isVisible;
                if (roleGranted.Attribute(Const.IsVisible).Value.TryParse(out isVisible, CultureInfo.InvariantCulture))
                {
                    role.IsVisible = isVisible; // set the visibility based on the attribute value
                }
            }

            return role;
        }
        #endregion protected MenuConfig reader

        #region IMenuConfigProvider Members
        public virtual IEnumerable<MenuItem> MenuItems
        {
            get
            {
                var menuItems = this.Cache()[Const.MenuItems + DataSourcePath] as IEnumerable<MenuItem>;
                if (menuItems.IsNull())
                {
                    menuItems = ReadMenuConfig();
                    using (var cacheDependency = new CacheDependency(DataSourcePath))
                    {
                        this.Cache().Insert(Const.MenuItems + DataSourcePath, menuItems, cacheDependency);
                    }
                }
                return menuItems;
            }
        }

        public virtual XDocument Menu
        {
            get { return DataSource; }
        }
        #endregion
    }
}